﻿using SCG.General;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//A single dungeon level, including a 2D map

namespace TTEngine
{
    public class DungeonLevel : IRayTracerRegion, IPathRegion
    {
        //public properties
        public DungeonTheme Theme { get; set; }
        public int Impulse { get; set; }
        public IDungeonHost Host { get; set; }

        public List<Room> Rooms
        {
            get
            {
                return _rooms;
            }
        }

        public CharMatrix TerrainMap
        {
            get
            {
                return _map;
            }
        }

        public List<MapObject>[,] ObjectMatrix
        {
            get
            {
                return _objects;
            }
        }

        public ByteMatrix IlluminationMatrix
        {
            get
            {
                return _illuminationMatrix;
            }
        }

        public BitMatrix VisibilityMatrix
        {
            get
            {
                return _visibilityMatrix;
            }
        }

        public List<MapObject> MapObjects
        {
            get
            {
                return _mapObjects;
            }
        }

        public List<Actor> Actors
        {
            get
            {
                return _actors;
            }
        }

        public int Left
        {
            get
            {
                int n = Int32.MaxValue;
                foreach (Room room in Rooms)
                    if (room.Left < n)
                        n = room.Left;
                return n;
            }
        }
        public int Top
        {
            get
            {
                int n = Int32.MaxValue;
                foreach (Room room in Rooms)
                    if (room.Top < n)
                        n = room.Top;
                return n;
            }
        }
        public int Right
        {
            get
            {
                int n = Int32.MinValue;
                foreach (Room room in Rooms)
                    if (room.Right > n)
                        n = room.Right;
                return n;
            }
        }
        public int Bottom
        {
            get
            {
                int n = Int32.MinValue;
                foreach (Room room in Rooms)
                    if (room.Bottom > n)
                        n = room.Bottom;
                return n;
            }
        }
        public int Width
        {
            get
            {
                return Right - Left + 1;
            }
        }
        public int Height
        {
            get
            {
                return Bottom - Top + 1;
            }
        }

        //generate the terrain map
        public void GenerateTerrainMap()
        {
            _map = new CharMatrix(Width + 2, Height + 2);
            _map.Fill(Globals.TERRAIN_WALL);
            foreach (Room room in Rooms)
            {
                for(int y = 0; y < room.Height; y++)
                    for (int x = 0; x < room.Width; x++)
                    {
                        char c = room.Matrix.Get(x, y);
                        if (c != Globals.TERRAIN_WALL)
                            _map.Set(x + room.X, y + room.Y, c);
                    }
            }

            //The Objects array is a 2d array, each cell contains a list of objects in that cell
            _objects = new List<MapObject>[Width, Height];

            //IlluminationMatrix tracks illumination of the areas
            _illuminationMatrix = new ByteMatrix(Width, Height);

            //VisibilityMatrix tracks which coordinates are visible to the player
            _visibilityMatrix = new BitMatrix(Width, Height);
        }

        //render to an image
        public Image Render(int width, int height)
        {
            float xmult = (float)width / (float)TerrainMap.Width;
            float ymult = (float)height / (float)TerrainMap.Height;
            SolidBrush bFloor = new SolidBrush(Color.White);
            SolidBrush bDoor = new SolidBrush(Color.Brown);
            Pen pObjects = new Pen(Color.Green);
            pObjects.Width = 2;
            Pen pMonsters = new Pen(Color.Red);
            Pen pHero = new Pen(Color.Blue);
            pHero.Width = 4;
            Bitmap bmp = new Bitmap(width, height);
            Graphics g = Graphics.FromImage(bmp);
            using (g)
            {
                g.Clear(Color.Black);
                for(int y = 0; y < TerrainMap.Height; y++)
                    for (int x = 0; x < TerrainMap.Width; x++)
                    {
                        if (TerrainMap.Get(x, y) != Globals.TERRAIN_WALL)
                        {
                            SolidBrush b = bFloor;
                            if (TerrainMap.Get(x, y) == Globals.TERRAIN_DOOR)
                                b = bDoor;
                            g.FillRectangle(b, xmult * x, ymult * y, xmult, ymult);
                        }
                    }
                foreach (MapObject mo in MapObjects)
                {
                    int tx = (int)(mo.X * xmult);
                    int ty = (int)(mo.Y * ymult);
                    g.DrawEllipse(mo.IsActor ? pMonsters : pObjects, tx, ty, 6, 6);
                }
                foreach (Actor member in Globals.Party.Members)
                {
                    int tx = (int)(member.X * xmult);
                    int ty = (int)(member.Y * ymult);
                    g.DrawEllipse(pHero, tx, ty, 6, 6);
                }

            }
            return bmp;
        }

        //find a terrain point
        public SCGPoint FindTerrain(int x, int y, char terrain)
        {
            int tx = x;
            int ty = y;
            SCGDirection dir = DirectionUtils.RandomDirection;
            while (TerrainMap.Get(tx, ty) != terrain)
                dir.Advance(ref tx, ref ty);
            return new SCGPoint(tx, ty);
        }

        //add an object to the map
        public void AddObjectToMap(MapObject mo, int x, int y)
        {
            //can we just increase the quantity of an item already there?
            if (mo.CanCollapse)
            {
                MapObject alreadyThere = GetObjectWithTag(x, y, mo.ObjectName);
                if (alreadyThere != null)
                {
                    alreadyThere.Quantity += mo.Quantity;
                    return;
                }
            }

            mo.Level = this;
            _mapObjects.Add(mo);
            if (mo.IsActor)
                _actors.Add(mo as Actor);
            List<MapObject> lst = _objects[x, y];
            if (lst == null)
            {
                lst = new List<MapObject>();
                _objects[x, y] = lst;
            }
            lst.Add(mo);
            mo.X = x;
            mo.Y = y;
            mo.AddedToMap();

            //auto-create sprite
            if (mo.Sprite == null)
                mo.Sprite = SpriteFactory.CreateSprite(mo);
            Host.SpriteAdded(mo);
        }

        //remove an object from the map
        public void RemoveObjectFromMap(MapObject mo)
        {
            List<MapObject> lst = ObjectMatrix[mo.X, mo.Y];
            if (lst != null)
                lst.Remove(mo);
            MapObjects.Remove(mo);
            if (mo.IsActor)
                Actors.Remove(mo as Actor);
            if (mo.Sprite != null)
                Host.SpriteRemoved(mo);
            mo.Sprite = null;
        }

        //move an object to a new location
        public void MoveObject(MapObject mo, int x, int y)
        {
            List<MapObject> lst = _objects[mo.X, mo.Y];
            lst.Remove(mo);
            mo.X = x;
            mo.Y = y;
            lst = _objects[mo.X, mo.Y];
            if (lst == null)
            {
                lst = new List<MapObject>();
                _objects[x, y] = lst;
            }
            lst.Add(mo);
        }

        //item lands in space after being thrown
        public void ThrownObjectLands(MapObject mo, int x, int y)
        {
            //TODO check for damage
            //check for breakage
            if (mo.BreaksWhenStressed(StressType.Thrown))
            {
                mo.Breaks(this, x, y);
                Host.SpriteExplodes(mo, x, y);
                return;
            }
           
            //add it to the map
            AddObjectToMap(mo, x, y);
        }

        //get an actor at the specified location
        public Actor GetActorAt(int x, int y, bool pcOnly)
        {
            if (!TerrainMap.InBounds(x, y))
                return null;
            List<MapObject> lst = ObjectMatrix[x, y];
            if (lst == null)
                return null;
            foreach(MapObject mo in lst)
                if (mo.IsActor)
                {
                    Actor actor = (Actor)mo;
                    if (actor.IsPC || !pcOnly)
                        return actor;
                }
            return null;
        }

        //spawn a group of monsters
        public Actor SpawnMonster(int x, int y, string monsterName)
        {
            Actor m = (Actor)MapObjectFactory.GetMapObject(monsterName);
            AddObjectToMap(m, x, y);
            return m;
        }
        public List<Actor> SpawnMonsters(int x, int y, string monsterName, int qty, int radius, int density)
        {
            List<Actor> lst = new List<Actor>();
            List<SCGPoint> pts = CreatePointCluster(qty, x, y, radius, density, '\0');
            foreach (SCGPoint pt in pts)
            {
                Actor m = SpawnMonster(pt.X, pt.Y, monsterName);
                lst.Add(m);
            }
            return lst;
        }

        //return a cluster of points
        public List<SCGPoint> CreatePointCluster(int qty, int x, int y, int radius, int density, char terrain)
        {
            List<SCGPoint> pts = new List<SCGPoint>();

            //create a master list of all possible points
            RandomList<SCGPoint> possiblePts = new RandomList<SCGPoint>();
            for (int py = y - radius; py <= y + radius; py++)
                for (int px = x - radius; px <= x + radius; px++)
                    if (TerrainMap.InBounds(px, py))
                        if (terrain == Globals.TERRAIN_WALL || IsEmpty(px, py))
                            if (terrain == '\0' || TerrainMap.Get(px, py) == terrain)
                                possiblePts.Add(new SCGPoint(px, py));

            //no points?
            if (possiblePts.Count == 0)
                return pts;

            do
            {
                //try a sampling of points and keep the one closest to the center
                SCGPoint pt = possiblePts.RandomValue;
                for (int d = 1; d < density; d++)
                {
                    SCGPoint challenge = possiblePts.RandomValue;
                    if (pt.DistanceQuick(x, y) > challenge.DistanceQuick(x, y))
                        pt = challenge;
                }
                pts.Add(pt);
                possiblePts.Remove(pt);
            }
            while (pts.Count < qty && possiblePts.Count > 0);

            return pts;
        }

        //distribute loot
        public void DistributeLoot(int minQty, int maxQty, int x, int y, int radius, int density)
        {
            List<SCGPoint> pts = CreatePointCluster(Globals.RandomIntFromTo(minQty, maxQty), x, y, radius, density, Globals.TERRAIN_FLOOR);
            foreach (SCGPoint pt in pts)
            {
                MapObject loot = Theme.GenerateLoot();
                AddObjectToMap(loot, pt.X, pt.Y);
            }
        }

        //is the location empty/traversible
        public bool IsEmpty(int x, int y)
        {
            if (IsOpenTerrain(x, y, true))
            {
                List<MapObject> lst = ObjectMatrix[x, y];
                if (lst == null)
                    return true;
                foreach (MapObject mo in lst)
                    if (mo.BlocksMovement)
                        return false;
                return true;
            }
            else
                return false;
        }

        //does the location have open terrain?
        public bool IsOpenTerrain(int x, int y, bool considerDoors)
        {
            char c = TerrainMap.GetSafely(x, y);
            if (c == Globals.TERRAIN_FLOOR || c == Globals.TERRAIN_PIT || c == Globals.TERRAIN_PLATFORM || c == Globals.TERRAIN_STAIRS)
                return true;
            else if (c == Globals.TERRAIN_DOOR)
                return !considerDoors;
            else
                return false;
        }

        //construct the illumination and visibility matrices
        public void Visibility()
        {
            _illuminationMatrix.Fill(0);
            foreach(MapObject mo in _mapObjects)
                if (mo.IllumMap != null)
                {
                    ByteMatrix im = mo.IllumMap;
                    int xOff = im.Width / 2;
                    int yOff = im.Height / 2;
                    _illuminationMatrix.Add(im, mo.X - xOff, mo.Y - yOff, 4);
                }

            foreach(Actor actor in _actors)
                foreach (MapObject gear in actor.ReadiedGear)
                    if (gear.IllumMap != null)
                    {
                        ByteMatrix im = gear.IllumMap;
                        int xOff = im.Width / 2;
                        int yOff = im.Height / 2;
                        _illuminationMatrix.Add(im, actor.X - xOff, actor.Y - yOff, 4);
                        }
            foreach (Actor pc in Globals.Party.Members)
                for (int x = -1; x <= 1; x++)
                    for (int y = -1; y <= 1; y++)
                    {
                        int mx = pc.X + x;
                        int my = pc.Y + y;
                        if (_illuminationMatrix.InBounds(mx, my))
                            if (_illuminationMatrix.Get(mx, my) == 0)
                                _illuminationMatrix.Set(mx, my, 1);
                    }

            //now that we have the illumination defined, define the areas visible to the PCs
            _visibilityMatrix.Clear();
            RayTracer rt = new RayTracer(this);
            foreach (Actor pc in Globals.Party.Members)
            {
                for (int x = -1; x <= 1; x++)
                    for (int y = -1; y <= 1; y++)
                        _visibilityMatrix.SetSafely(pc.X + x, pc.Y + y, true);
                List<SCGPoint> pts = rt.RayTrace(pc.X, pc.Y, 20);
                foreach (SCGPoint pt in pts)
                    _visibilityMatrix.SetSafely(pt.X, pt.Y, true);
            }
        }

        //place the party members
        public void PlacePartyInEntrance()
        {
            Room entrance = Rooms[0];
            List<SCGPoint> pts = CreatePointCluster(Globals.Party.Members.Count, entrance.CenterX, entrance.CenterY, 5, 5, Globals.TERRAIN_FLOOR);
            for (int n = 0; n < pts.Count; n++)
                AddObjectToMap(Globals.Party.Members[n], pts[n].X, pts[n].Y);
        }

        //process an impulse
        public void ProcessImpulse()
        {
            //increase impulse number
            Impulse++;

            //make a list of actors that can move this impulse
            RandomList<Actor> moving = new RandomList<Actor>();
            foreach (Actor actor in Actors)
            {
                //process their conditions
                if (actor.HasConditions)
                {
                    List<Condition> cl = new List<Condition>(actor.Conditions);
                    foreach (Condition c in cl)
                        c.ProcessImpulse();
                }

                //can they move
                if (_impulseActions.CanAct(actor.CurrentMovement, Impulse))
                    moving.Add(actor);
            }

            //process each actor that can move
            while (moving.Count > 0)
            {
                Actor actor = moving.RemoveRandomValue;

                //something in there square prohibits move?
                bool moveProhibited = false;
                List<MapObject> lst = ObjectMatrix[actor.X, actor.Y];
                if (lst != null)
                    foreach(MapObject mo in lst)
                        if (mo.ProhibitsMovement(actor))
                        {
                            moveProhibited = true;
                            break;
                        }
                if (moveProhibited)
                    continue;

                //process orders
                int oldX = actor.X;
                int oldY = actor.Y;
                switch (actor.Orders)
                {
                    case OrderType.Move:
                        {
                            if (actor.Position.EqualsPoint(actor.Destination))
                            {
                                actor.Orders = OrderType.None;
                                continue;
                            }
                            MoveToward(actor, actor.DestX, actor.DestY);
                            if (actor.X == actor.DestX && actor.Y == actor.DestY)
                                actor.Orders = OrderType.None;
                            break;
                        }
                    case OrderType.Escort:
                        {
                            int dx = actor.Target.X + actor.DestX;
                            int dy = actor.Target.Y + actor.DestY;
                            if (actor.X == dx && actor.Y == dy)
                                continue;
                            MoveToward(actor, dx, dy);
                            break;
                        }
                    case OrderType.MoveRandomly:
                        {
                            SCGDirection8 dir = DirectionUtils.RandomDirection8;
                            int x = actor.X;
                            int y = actor.Y;
                            dir.Advance(ref x, ref y);
                            if (CostFrom(actor.Position, new SCGPoint(x, y)) < 9999)
                            {
                                MoveObject(actor, x, y);
                                Host.ActorMoved(actor, oldX, oldY);
                            }
                            break;
                        }
                    case OrderType.Climb:
                        {
                            char climbingTo = TerrainMap.Get(actor.DestX, actor.DestY);
                            int l1 = TerrainLevel(actor.X, actor.Y);
                            int l2 = TerrainLevel(actor.DestX, actor.DestY);
                            if (actor.PerformTest(Abilities.Strength, 3, 20))
                            {
                                //climb successful, move them into target square
                                string dir = l1 < l2 ? "up" : "down";
                                Host.DisplayMessage(actor.DisplayName + " climbs " + dir + " to the " + climbingTo.TerrainDescription() + ".", actor);
                                MoveObject(actor, actor.DestX, actor.DestY);
                                Host.ActorMoved(actor, oldX, oldY);
                                actor.Orders = OrderType.None;
                            }
                            else
                            {
                                //climb unsuccessful, make a check to see if they slip and fall
                                if (!actor.PerformTest(Abilities.Dexterity, 2, 0))
                                {
                                    //climbing gear prevents fall
                                    MapObject gear = actor.GetGearWithTag("Climbing Gear");
                                    if (gear != null)
                                    {
                                        Host.DisplayMessage("The " + gear.DisplayName + " prevents " + actor.DisplayName + " from falling!", actor);
                                        if (gear.BreaksWhenStressed(StressType.UsedForPrimaryPurpose))
                                        {
                                            gear.Breaks(this, actor.X, actor.Y);
                                            gear.ConsumeOne();
                                        }
                                    }
                                    else
                                    {
                                        //when clibming up fall back into source square, when down into destination square
                                        int tx, ty;
                                        if (l1 > l2)
                                        {
                                            tx = actor.DestX;
                                            ty = actor.DestY;
                                        }
                                        else
                                        {
                                            tx = actor.X;
                                            ty = actor.Y;
                                        }
                                        MoveObject(actor, tx, ty);
                                        Host.ActorMoved(actor, oldX, oldY);
                                        actor.Orders = OrderType.None;
                                        string msg = actor.DisplayName + " falls!";
                                        actor.ApplyDamage(Globals.RandomIntFromTo(1, 2), "b", msg);
                                        Host.BloodSplatter(actor.X, actor.Y);
                                    }
                                }
                            }
                            break;
                        }
                }
            }

            //process visibility
            Visibility();

            //process map objects
            List<MapObject> mos = new List<MapObject>(MapObjects);
            foreach (MapObject mo in mos)
                mo.ProcessImpulse();

            //process gear
            foreach (Actor actor in Actors)
            {
                List<MapObject> mosGear = new List<MapObject>(actor.Gear);
                foreach (MapObject gear in mosGear)
                    gear.ProcessImpulse();
            }

            //process rooms
            foreach (Room room in Rooms)
                room.ProcessImpulse();

            //attacks
            List<Actor> acting = new List<Actor>(Actors);
            foreach (Actor actor in acting)
            {
                if (actor.WasKilled)
                    continue;

                if (actor.AttackCounter <= 0)
                {
                    Actor enemy = actor.AdjacentEnemy;
                    if (enemy != null)
                    {
                        Host.LungeSprite(actor, enemy);
                        bool damaged = false;

                        //weapon?
                        MapObject wpn = actor.Weapon;
                        if (wpn != null)
                        {
                            string msg = actor.DisplayName + " " + wpn.AttackName + " " + enemy.DisplayName + " with " + wpn.DisplayName + ".";
                            foreach (DamageExpression de in wpn.Damage)
                                if (enemy.ApplyDamage(de.DamageValue, de.DamageType, msg))
                                {
                                    damaged = true;
                                    msg = "";
                                }
                            actor.AttackCounter = wpn.WeaponSpeed;

                            //weapon breaks?
                            if (wpn.BreaksWhenStressed(StressType.UsedAsWeapon))
                            {
                                Host.DisplayMessage(actor.DisplayName + "'s " + wpn.DisplayName + " breaks!");
                                actor.UnreadyGear(wpn);
                                wpn.ConsumeOne();
                            }
                        }
                        else if (actor.Damage != null)
                        {
                            //natural attacks
                            string msg = actor.DisplayName + " " + actor.AttackName + " " + enemy.DisplayName + ".";
                            foreach (DamageExpression de in actor.Damage)
                                if (enemy.ApplyDamage(de.DamageValue, de.DamageType, msg))
                                {
                                    damaged = true;
                                    msg = "";
                                }
                            actor.AttackCounter = actor.WeaponSpeed;
                        }

                        //blood splatter
                        if (damaged)
                            Host.BloodSplatter(enemy.X, enemy.Y);
                    }
                }
                else
                    actor.AttackCounter--;
            }

            //update UI
            Host.ImpulseProcessed();
        }
        private bool GetMoveDirection(Actor actor, SCGDirection8 dir, bool turnRight, int turns, ref int x, ref int y)
        {
            x = actor.X;
            y = actor.Y;
            while (turns > 0)
            {
                if (turnRight)
                    dir = dir.TurnRight();
                else
                    dir = dir.TurnLeft();
                turns--;
            }
            dir.Advance(ref x, ref y);
            return CostFrom(actor.Position, new SCGPoint(x, y)) < 9999;
        }
        private void MoveToward(Actor actor, int destx, int desty)
        {
            int oldX = actor.X;
            int oldY = actor.Y;
            int x = 0;
            int y = 0;
            SCGPoint dest = new SCGPoint(destx, desty);
            PathFinder pf = new PathFinder(this);
            Globals.PathLooker = actor;
            Path path = pf.FindPath(actor.Position, dest, 100);
            if (path != null)
            {
                SCGPoint pt = path.Zones[1];
                x = pt.X;
                y = pt.Y;
            }
            else
            {
                SCGDirection8 dir = actor.Position.DirectionToward8(destx, desty);
                x = actor.X;
                y = actor.Y;
                dir.Advance(ref x, ref y);
                if (CostFrom(actor.Position, new SCGPoint(x, y)) >= 9999)
                    if (!GetMoveDirection(actor, dir, true, 1, ref x, ref y))
                        if (!GetMoveDirection(actor, dir, false, 1, ref x, ref y))
                            if (!GetMoveDirection(actor, dir, true, 2, ref x, ref y))
                                if (!GetMoveDirection(actor, dir, false, 2, ref x, ref y))
                                    return;
            }
            MoveObject(actor, x, y);
            Host.ActorMoved(actor, oldX, oldY);
        }

        //get an adjacent point for purposes of spreading fluids
        public SCGPoint GetSpreadPoint(int x, int y, string excludeTag, bool considerDoors)
        {
            if (IsOpenTerrain(x, y, considerDoors))
                if (GetObjectWithTag(x, y, excludeTag) == null)
                    return new SCGPoint(x, y);

            char t1 = TerrainMap.Get(x, y);
            int attempts = 0;
            int tx, ty;
            int l1 = TerrainLevel(x, y);
            bool found = false;
            do
            {
                tx = x;
                ty = y;
                SCGDirection dir = DirectionUtils.RandomDirection;
                dir.Advance(ref tx, ref ty);
                found = true;
                if (GetObjectWithTag(tx, ty, excludeTag) != null)
                    found = false;
                else if (!IsOpenTerrain(tx, ty, considerDoors))
                {
                    //revert back
                    tx = x;
                    ty = y;
                    found = false;
                }

                //can't spread to a higher level
                int l2 = TerrainLevel(tx, ty);
                if (l2 > l1)
                {
                    tx = x;
                    ty = y;
                    found = false;
                }

                //break after 25 attempts
                if (!found)
                {
                    attempts++;
                    if (attempts > 25)
                        return null;
                    x = tx;
                    y = ty;
                }
            }
            while (!found);
            return new SCGPoint(tx, ty);
        }

        //return a mapobject in the space that has a specified tag
        public MapObject GetObjectWithTag(int x, int y, string tag)
        {
            List<MapObject> lst = ObjectMatrix[x, y];
            if (lst == null)
                return null;
            foreach (MapObject mo in lst)
                if (mo.HasTag(tag))
                    return mo;
            return null;
        }

        //get the "level" of the terrain at a given space
        public int TerrainLevel(int x, int y)
        {
            char t1 = TerrainMap.Get(x, y);
            if (t1 == Globals.TERRAIN_PIT)
                return -1;
            else if (t1 == Globals.TERRAIN_PLATFORM)
                return 1;
            else
                return 0;
        }

        //private members
        private List<Room> _rooms = new List<Room>();
        private List<MapObject> _mapObjects = new List<MapObject>();
        private List<Actor> _actors = new List<Actor>();
        private CharMatrix _map = null;
        private List<MapObject>[,] _objects = null;
        private ByteMatrix _illuminationMatrix = null;
        private BitMatrix _visibilityMatrix = null;
        private ImpulseActions _impulseActions = new ImpulseActions(20);

        #region IRayTraceRegion

        public bool BlocksLOS(int x, int y)
        {
            return !IsOpenTerrain(x, y, true);
        }

        public bool InBounds(int x, int y)
        {
            return TerrainMap.InBounds(x, y);
        }

        #endregion

        #region IPathRegion

        //get cost from one zone to another
        public int CostFrom(SCGPoint from, SCGPoint to)
        {
            if (!IsEmpty(to.X, to.Y))
                return 9999;
            if (!_visibilityMatrix.Get(to.X, to.Y))
                return 9999;

            //avoid counting corners as valid points
            int diffX = to.X - from.X;
            int diffY = to.Y - from.Y;
            if (!CheckCorner(from, diffX, diffY, 1, -1, 0, -1, 1, 0))
                return 9999;
            if (!CheckCorner(from, diffX, diffY, 1, 1, 0, 1, 1, 0))
                return 9999;
            if (!CheckCorner(from, diffX, diffY, -1, 1, -1, 0, 0, 1))
                return 9999;
            if (!CheckCorner(from, diffX, diffY, -1, -1, -1, 0, 0, -1))
                return 9999;

            //don't consider climbing part of normal path, but always allow climbing stairs
            if (Globals.PathLooker != null)
            {
                bool canScaleWalls = Globals.PathLooker.HasTag("Flyer") || Globals.PathLooker.HasTag("WallCrawler");
                if (!canScaleWalls)
                    if (TerrainLevel(from.X, from.Y) != TerrainLevel(to.X, to.Y))
                        if (TerrainMap.Get(from.X, from.Y) != Globals.TERRAIN_STAIRS && TerrainMap.Get(to.X, to.Y) != Globals.TERRAIN_STAIRS)
                            return 9999;
            }

            diffX = Math.Abs(diffX);
            diffY = Math.Abs(diffY);
            if (diffX == 1 && diffY == 1)
                return 3;
            else
                return 2;
        }
        private bool CheckCorner(SCGPoint pt, int diffX, int diffY, int testX, int testY, int offX, int offY, int offX2, int offY2)
        {
            if (diffX == testX && diffY == testY)
                if (!(IsOpenTerrain(pt.X + offX, pt.Y + offY, false) && IsOpenTerrain(pt.X + offX2, pt.Y + offY2, false)))
                    return false;
            return true;
        }

        public List<SCGPoint> GetNeighbors(SCGPoint pt)
        {
            List<SCGPoint> pts = new List<SCGPoint>();
            for(int y = -1; y <= 1; y++)
                for(int x = -1; x <= 1; x++)
                    if (x != 0 || y != 0)
                    {
                        int tx = pt.X + x;
                        int ty = pt.Y + y;
                        if (TerrainMap.InBounds(tx, ty))
                            pts.Add(new SCGPoint(tx, ty));
                    }
            return pts;
        }

        #endregion
    }
}   
